#ifndef __OCTREE__
#define __OCTREE__


#include "..\RayTracing\BVH.h"
#include <vector>
#include "..\RayTracing\Point3D.h"
#include <queue>
#include "..\RayTracing\Common.h"
#include <string>

#define BUCKET_LIMIT 1
#define AMOUNT_OF_POINTS_FOR_ESTIMATE 0
#define MAXIMUM_LEVEL 8

namespace Engine
{
	using std::queue;
	using std::string;

	struct RadiancePoint
	{
		Point3D m_position;

		RadiancePoint(Point3D p_position)
		{
			m_position = p_position;
		}

		string ToString()
		{
			return m_position.toString();
		}
	};

	class OctNode
	{
		friend class Octree;

	private:
		BoundingBox *m_boundingBos;
		OctNode* children[8];
		vector<RadiancePoint*> m_storedPoints;
		bool childNode;
		bool parentNode;
		int level;
	public:

		bool IsParentNode()
		{
			return parentNode;
		}

		OctNode(Point3D& minPoint, Point3D& maxPoint, int& p_level)
		{
			childNode = true;
			m_boundingBos = new BoundingBox(minPoint, maxPoint);
			parentNode = false;
			level = p_level;
		}

		OctNode(BoundingBox* boundingBox)
		{
			childNode = true;
			m_boundingBos = boundingBox;
			parentNode = false;
			level = 0;
		}

		OctNode()
		{
			m_boundingBos = new BoundingBox(0);
			level = 0;
		}

		void InsertPoint(RadiancePoint *point)
		{
			//Print("inserting point" + point->ToString());
			if ((m_storedPoints.size() + 1 <= BUCKET_LIMIT && !parentNode) || level > MAXIMUM_LEVEL)
			{
				//string toAdd = "adding the following point "+point->ToString();
				//Print(toAdd);
				m_storedPoints.push_back(point);
			}
			else
			{
				//Print("Splitting box");
				SplitBox();
				childNode = false;
				parentNode = true;
				Point3D previousPoint;
				//add the currently being added node 
				m_storedPoints.push_back(point);
				for (int index = 0; index < m_storedPoints.size(); index++)
				{
					RadiancePoint *currentPoint = m_storedPoints[index];

					for (int boudningBoxSize = 0; boudningBoxSize < 8; boudningBoxSize++)
					{
						OctNode* currentChild = children[boudningBoxSize];

						if (currentChild->m_boundingBos->CheckPointInsideBox(currentPoint->m_position))
						{
							Print("adding point " + currentPoint->ToString() + " to the current " + to_string(boudningBoxSize) + "-" + currentChild->m_boundingBos->ToString());
							currentChild->InsertPoint(currentPoint);

						}
					}
				}
				/*EmptyPointerVector<RadiancePoint>(m_storedPoints);
				m_storedPoints = vector<RadiancePoint*>();*/
				m_storedPoints.clear();
			}
		}

		bool CheckIntersectionWithPoint(RadiancePoint* radiancePoint)
		{
			return (this->m_boundingBos->CheckPointInsideBox(radiancePoint->m_position));
		}

		bool CheckIntersectionWithPoint(Point3D& point)
		{
			return (this->m_boundingBos->CheckPointInsideBox(point));
		}

		vector<RadiancePoint*> GetPoints(Point3D& radiancePoint)
		{
			return m_storedPoints;
		}

		/*vector<RadiancePoint*>& GetRadiancePointsInRange(RadiancePoint* p_currentPoint)
		{
			vector<RadiancePoint*> tempRadianceList;

			for (int pointIndex = 0; pointIndex < tempRadianceList.size(); pointIndex++)
			{
				RadiancePoint* currentPoint = m_storedPoints[pointIndex];
				if (currentPoint->GetWeighting(p_currentPoint) > 0)
				{
					tempRadianceList.push_back(currentPoint);
				}
			}
			return tempRadianceList;
		}*/



		void SplitBox()
		{
			cout << " Box Being Split" << endl;
			Point3D centoridPoint = m_boundingBos->GetCentroid();
			Point3D tempMinPoint = Point3D(0), tempMaxPoint = Point3D(0);

			Point3D minPoint = this->m_boundingBos->GetMinPoint();
			Point3D maxPoint = this->m_boundingBos->GetMaxPoint();


			//min - centroid 
			tempMinPoint = minPoint;
			tempMaxPoint = centoridPoint;
			children[0] = new OctNode(tempMinPoint, tempMaxPoint, ++level);

			//xmin bmin zc-- xc yc zmax
			tempMinPoint = Point3D(minPoint.x, minPoint.y, centoridPoint.z);
			tempMaxPoint = Point3D(centoridPoint.x, centoridPoint.y, maxPoint.z);

			children[1] = new OctNode(tempMinPoint, tempMaxPoint, ++level);

			//xc ymin zc  xmax yc zmax
			tempMinPoint = Point3D(centoridPoint.x, minPoint.y, centoridPoint.z);
			tempMaxPoint = Point3D(maxPoint.x, centoridPoint.y, maxPoint.z);

			children[2] = new OctNode(tempMinPoint, tempMaxPoint, ++level);

			//xc ymin cmin - xmax yc zc
			tempMinPoint = Point3D(centoridPoint.x, minPoint.y, minPoint.z);
			tempMaxPoint = Point3D(maxPoint.x, centoridPoint.y, centoridPoint.z);

			children[3] = new OctNode(tempMinPoint, tempMaxPoint, ++level);

			//xc yc zmin -- xmax ymax zc
			tempMinPoint = Point3D(centoridPoint.x, centoridPoint.y, minPoint.z);
			tempMaxPoint = Point3D(maxPoint.x, maxPoint.y, centoridPoint.z);

			children[4] = new OctNode(tempMinPoint, tempMaxPoint, ++level);

			//xmin yc zmin -- xc ymax nc
			tempMinPoint = Point3D(minPoint.x, centoridPoint.y, minPoint.z);
			tempMaxPoint = Point3D(centoridPoint.x, maxPoint.y, centoridPoint.z);

			children[5] = new OctNode(tempMinPoint, tempMaxPoint, ++level);


			//xmin yc nc -- xc ymax zmax
			tempMinPoint = Point3D(minPoint.x, centoridPoint.y, centoridPoint.z);
			tempMaxPoint = Point3D(centoridPoint.x, maxPoint.y, maxPoint.z);

			children[6] = new OctNode(tempMinPoint, tempMaxPoint, ++level);

			//centroid - Max
			children[7] = new OctNode(centoridPoint, maxPoint, ++level);
		}

		RadiancePoint* GetPoint(RadiancePoint* p_toFind)
		{
			if (childNode)
 			{
				for (int index = 0; index < m_storedPoints.size(); index++)
				{
					if (m_storedPoints[index] == p_toFind)
					{
						return m_storedPoints[index];
					}
				}
			}
			else
				return NULL;

			return NULL;
		}

		RadiancePoint* GetPoint(Point3D& toFind)
		{

			RadiancePoint *tempPoint = new RadiancePoint(toFind);
			return GetPoint(tempPoint);
		}


		bool IsChildNode()
		{
			return childNode;
		}

		string ToString()
		{
			return m_boundingBos->ToString();
		}
	};


	class Octree
	{
	public:

		Octree(BVH* p_sceneRepresentation)
		{
			m_rootNode = new OctNode(p_sceneRepresentation->rootNode->boundingBox);
		}

		Octree()
		{
			m_rootNode = new OctNode();
		}

		void InsertPoint(RadiancePoint* p_toInst)
		{
			InsertPoint(p_toInst, m_rootNode);
		}

		bool GetPoints(RadiancePoint*  pointToGet, vector<RadiancePoint*>& p_availablePoints)
		{
			GetRadiancePoints(pointToGet, p_availablePoints);
			return p_availablePoints.size() > AMOUNT_OF_POINTS_FOR_ESTIMATE;
		}
		//check what the bug is 
		void GetRadiancePoints(RadiancePoint*  pointToGet, vector<RadiancePoint*>& p_availablePoints)
		{
			OctNode* currentNode = m_rootNode;
			bool foundChildNode = false;
			while (!foundChildNode)
			{

				if (currentNode->childNode)
				{
					for (int pointIndex = 0; pointIndex <currentNode->m_storedPoints.size(); pointIndex++)
					{
						RadiancePoint* currentPoint = currentNode->m_storedPoints[pointIndex];
					//	double weight = currentPoint->GetWeighting(pointToGet);
					/*	if (weight > 0)
						{
							p_availablePoints.push_back(currentPoint);
						}*/
					}

					foundChildNode = true;
				}
				else
				{
					bool nodeFound = false;
					for (int index = 0; index < 8; index++)
					{
						OctNode* currentSubNode = currentNode->children[index];

						if (currentSubNode->m_boundingBos->CheckPointInsideBox(pointToGet->m_position))
						{
							currentNode = currentSubNode;
							nodeFound = true;
							break;
						}

						//TODO might need to fix here
					}

					if (!nodeFound)
						foundChildNode = true;
				}
			}
		}

		bool CheckIfExist(Point3D& p_postion)
		{
			RadiancePoint* toFind = GetRadiancePoint(p_postion);
			return toFind != NULL;
		}

		RadiancePoint* GetRadiancePoint(Point3D p_position)
		{
			RadiancePoint* toFind = NULL;
			OctNode * currentNode = m_rootNode;
			bool childNodeFound = false;
			while (!childNodeFound)
			{
				if (currentNode->IsChildNode())
				{
					for (int index = 0; index < currentNode->m_storedPoints.size(); index++)
					{
						if (currentNode->m_storedPoints[index]->m_position == p_position)
						{
							toFind = currentNode->m_storedPoints[index];
						}
					}
					childNodeFound = true;
				}
				else
				{
					bool nextNode = false;

					for (int nodeIndex = 0; nodeIndex < 8; nodeIndex++)
					{
						if (currentNode->children[nodeIndex]->CheckIntersectionWithPoint(p_position))
						{
							currentNode = currentNode->children[nodeIndex];
							nextNode = true;
							break;
						}
					}

					if (!nextNode)
						childNodeFound = true;
				}
			}

			return toFind;
		}

		~Octree()
		{
			delete m_rootNode;
		}


	private:
		OctNode *m_rootNode;

		int level;
		void InsertPoint(RadiancePoint* p_toInst, OctNode* p_currentNode)
		{
			if (p_currentNode->IsChildNode())
			{

				cout << p_toInst->m_position.toString() << endl;
				p_currentNode->InsertPoint(p_toInst);
			}
			else
			{
				for (int childIndex = 0; childIndex < 8; childIndex++)
				{
					OctNode* currentChildNode = p_currentNode->children[childIndex];
					if (currentChildNode->CheckIntersectionWithPoint(p_toInst))

						InsertPoint(p_toInst, currentChildNode);
				}
			}
		}
	};




}

#endif // !__OCTREE__
